package yawning.mew.character;

import yawning.mew.LevelDep;
import yawning.mew.ProcEffect.Effect;
import yawning.mew.sim.SimulationEngine;
import yawning.mew.sim.SimulationProc.EffectCallback;
import ec.util.MersenneTwisterFast;

public abstract class CharacterModel {
	protected Toon mToon;
	protected LevelDep mLevelDep;

	// Proc effects.
	public double bonusAgility = 0.0;
	public double bonusStrength = 0.0;
	public double bonusAttackPower = 0.0;
	public double bonusCritRating = 0.0;
	public double bonusHasteRating = 0.0;
	public double bonusMasteryRating = 0.0;
	public double bonusHitRating = 0.0;
	public double bonusExpertiseRating = 0.0;
	public double bonusWeaponDamage = 0.0;

	public double bonusMeleeCrit = 0.0;
	public double bonusSpellCrit = 0.0;

	public double agiMult = 1.0;
	public double strMult = 1.0;
	public double apMult = 1.0;
	public double hasteMult = 1.0;
	public double meleeHasteMult = 1.0;

	protected CharacterModel(Toon ch, LevelDep ld) {
		mToon = ch;
		mLevelDep = ld;
	}

	public double getMissChance() {
		double plusHit = (mToon.hitRating + bonusHitRating) / mLevelDep.getHitConv() / 100;
		return (plusHit > mLevelDep.getPBaseMiss()) ? 0 : mLevelDep.getPBaseMiss() - plusHit;
	}

	public double getDodgeChance() {
		double minusDodge = (mToon.expertiseRating + bonusExpertiseRating) / mLevelDep.getExpConv() / 100;
		return (minusDodge > mLevelDep.getPBaseDodge()) ? 0 : mLevelDep.getPBaseDodge() - minusDodge;
	}

	public double getSpellMissChance() {
		double plusSpellHit = (mToon.hitRating + bonusHitRating) / mLevelDep.getSpellHitConv() / 100;
		return plusSpellHit > mLevelDep.getPBaseSpellMiss() ? 0 : mLevelDep.getPBaseSpellMiss() - plusSpellHit;
	}

	public double getSpellCritChance() {
		// XXX: Need to add in int conversion
		double pCritRCrit = (mToon.critRating + bonusCritRating) / mLevelDep.getCritConv() / 100;
		return Math.min(1, bonusSpellCrit + pCritRCrit);
	}

	public double getMeleeCritChance() {
		double pAgiCrit = getAgility() / mLevelDep.getAgiConv() / 100;
		double pCritRCrit = (mToon.critRating + bonusCritRating) / mLevelDep.getCritConv() / 100;
		double pCritSuppression = mLevelDep.getPCritSuppression();
		double pRawCrit = bonusMeleeCrit + pAgiCrit + pCritRCrit;
		return Math.max(Math.min(pRawCrit - pCritSuppression, 1.0), 0);
	}

	public double getHaste() {
		double hasteRating = mToon.hasteRating + bonusHasteRating;
		double hasteRHaste = 1 + (hasteRating / mLevelDep.getHasteConv() / 100);
		return hasteRHaste * hasteMult;
	}

	public double getMeleeInterval() {
		double haste = getHaste() * meleeHasteMult;
		return mToon.weaponSpeed / haste / 1000;
	}

	public double getMastery() {
		double masteryRMast = (mToon.masteryRating + bonusMasteryRating) / mLevelDep.getMasteryConv();
		return mLevelDep.getBaseMastery() + masteryRMast;
	}

	public abstract double getAttackPower();

	public double getAgility() {
		return (mToon.agility + bonusAgility) * agiMult;
	}

	public double getStrength() {
		return (mToon.strength + bonusStrength) * strMult;
	}

	public double getWeaponDamage() {
		return 1 + (double)(mToon.weaponTop + mToon.weaponBottom) / 2  + bonusWeaponDamage + getAttackPower() * mToon.weaponSpeed / 14000;
	}

	public double getWhiteCritChance() {
		return Math.min(getMeleeCritChance(), 1 - (getDodgeChance() + getMissChance() + mLevelDep.getPGlance()));
	}

	public double getWhiteCritCap() {
		return 1 - getDodgeChance() - getMissChance() - mLevelDep.getPGlance();
	}

	// Everything below here is specific to Simulations

	private MersenneTwisterFast rng;
	private SimulationEngine mSimUtil;


	public void setSimulationEngine(SimulationEngine se) {
		mSimUtil = se;
		rng = mSimUtil.getRandom();

		mSimUtil.registerProcEffectCallback(Effect.AGILITY, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusAgility += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.ATTACK_POWER, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusAttackPower += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.CRIT_RATING, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusCritRating += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.EXPERTISE_RATING, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusExpertiseRating += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.HASTE_RATING, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusHasteRating += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.HIT_RATING, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusHitRating += n;
			}
		});
		mSimUtil.registerProcEffectCallback(Effect.MASTERY_RATING, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusMasteryRating += n;
			}
		});

		// TODO:
//		mSimUtil.registerProcEffectCallback(Effect.PHYSICAL_DAMAGE, new EffectCallback() {
//			@Override
//			public void onEffect(int n) {
//				AttackResult result = getYellowResult();
//				double damage = n;
//
//				if (result != AttackResult.MISS && result != AttackResult.DODGE) {
//					if (result == AttackResult.CRIT) {
//						damage *= multCrit;
//					}
//
//					damage *= getDirectDamageMult();
//					procPhysDamage += Math.round(damage);
//				}
//			}
//		});
//		mSimUtil.registerProcEffectCallback(Effect.SPELL_DAMAGE, new EffectCallback() {
//			@Override
//			public void onEffect(int n) {
//				AttackResult result = getSpellResult();
//				double damage = n;
//
//				if (result != AttackResult.MISS) {
//					if (result == AttackResult.CRIT) {
//						damage *= multSpellCrit;
//					}
//
//					// XXX: Factor in Spell Vulnerability here.
//					procSpellDamage += Math.round(damage);
//				}
//			}
//		});
		mSimUtil.registerProcEffectCallback(Effect.STRENGTH, new EffectCallback() {
			@Override
			public void onEffect(int n) {
				bonusStrength += n;
			}
		});
	}

	public enum AttackResult { MISS, DODGE, GLANCE, CRIT, HIT };

	public AttackResult getWhiteResult() {
		double roll = rng.nextDouble();
		double pMiss = getMissChance();
		double pDodge = getDodgeChance();
		double pGlance = mLevelDep.getPGlance();
		double pWhiteCrit = Math.min(getMeleeCritChance(), 1 - (pDodge + pMiss + pGlance));

		// White swings are one roll.
		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pGlance) < 0) {
			return AttackResult.GLANCE;
		} else if ((roll -= pWhiteCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowResult() {
		double roll = rng.nextDouble();
		double pMiss = getMissChance();
		double pDodge = getDodgeChance();
		double pCrit = getMeleeCritChance() * (1.0 - (pMiss + pDodge));	// Yellow swings are two roll.

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowResult(double pBonusCrit) {
		double roll = rng.nextDouble();
		double pMiss = getMissChance();
		double pDodge = getDodgeChance();
		double pCrit = Math.min(1, getMeleeCritChance() + pBonusCrit) * (1.0 - (pMiss + pDodge));

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pDodge) < 0) {
			return AttackResult.DODGE;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getYellowStealthResult(double pBonusCrit) {
		double roll = rng.nextDouble();
		double pMiss = getMissChance();
		double pCrit = Math.min(1, getMeleeCritChance() + pBonusCrit) * (1.0 - pMiss);

		if ((roll -= pMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}

	public AttackResult getDotResult(double pCrit) {
		boolean roll = rng.nextBoolean(pCrit);
		return (roll) ? AttackResult.CRIT : AttackResult.HIT;
	}

	public AttackResult getSpellResult() {
		double roll = rng.nextDouble();
		double pSpellMiss = getSpellMissChance();
		double pSpellCrit = getSpellCritChance();

		if ((roll -= pSpellMiss) < 0) {
			return AttackResult.MISS;
		} else if ((roll -= pSpellCrit) < 0) {
			return AttackResult.CRIT;
		}

		return AttackResult.HIT;
	}
}
